/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package kpsmart.model.routes;

import java.util.*;
import kpsmart.model.events.*;

/**
 * Routes utility class for everything that is connected to routes
 *
 * @author Oliver Behncke
 */
public final class Routes {

    private static final Set<String> DISTRIBUTION_CENTRES = new HashSet<String>(Arrays.asList("Auckland", "Hamilton", "Rotorua", "Palmerston North", "Wellington", "Christchurch", "Dunedin"));
    private static final List<String> PRIORITIES = new ArrayList<String>(Arrays.asList("International Air", "International Standard", "Domestic Air", "Domestic Standard"));
    private static final List<String> ROUTE_TYPES = new ArrayList<String>(Arrays.asList("Air", "Land", "Sea"));
    private static final List<String> DAYS = new ArrayList<String>(Arrays.asList("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"));

    private Routes() {
    }

    /**
     * Compute which routes were used for the mail types of a given event list
     *
     * @param events
     * @return Map<MailType,ChainedRoute>
     */
    public static Map<MailType, ChainedRoute> getUsedRoutes(List<Event> events) {
        List<MailType> mails = Events.getMailTypes(events);
        Map<MailType, ChainedRoute> result = new HashMap<MailType, ChainedRoute>();
        for (MailType m : mails) {
            result.put(m, getUsedRoute(events, m));
        }
        return result;
    }

    /**
     * Compute which route was used for a given mail type
     *
     * @param events
     * @param m
     * @return ChainedRoute used route
     */
    public static ChainedRoute getUsedRoute(List<Event> events, MailType m) {
        ChainedRoute route = getRoute(Events.dropEventsAfter(events, m), m);
        if (route == null) {
            throw new RuntimeException("Database in illegal state, couldn't compute used route for existing mail item");
        } else {
            return route;
        }
    }

    /**
     * Compute which routes were used for the mail types of a given event list
     * and map of used routes
     *
     * @param events
     * @param usedRoutes
     * @return Map<MailType,Integer>
     */
    public static Map<MailType, Integer> getUsedPrices(List<Event> events, Map<MailType, ChainedRoute> usedRoutes) {
        Map<MailType, Integer> result = new HashMap<MailType, Integer>();
        for (MailType m : usedRoutes.keySet()) {
            result.put(m, getUsedPrice(events, usedRoutes.get(m), m));
        }
        return result;
    }

    /**
     * Compute which price was used for a given mail type
     *
     * @param events
     * @param route
     * @param m
     * @return ChainedRoute used route
     */
    public static int getUsedPrice(List<Event> events, ChainedRoute route, MailType m) {
        int price = getPriceForRoute(Events.dropEventsAfter(events, m), route, m.getPriority(), m.getWeight().intValue(), m.getVolume().intValue());
        if (price == -1) {
            throw new RuntimeException("Database in illegal state, couldn't compute used price for existing mail item");
        } else {
            return price;
        }
    }

    /**
     * Get costs of mail types for map of used routes
     *
     * @param usedRoutes
     * @return
     */
    public static Map<MailType, Integer> getCostsOfMails(Map<MailType, ChainedRoute> usedRoutes) {
        Map<MailType, Integer> result = new HashMap<MailType, Integer>();
        for (MailType m : usedRoutes.keySet()) {
            result.put(m, getCostOfMail(usedRoutes.get(m), m));
        }
        return result;
    }

    /**
     * Get overall cost for a route and a given mail type
     *
     * @param route
     * @param mail
     * @return
     */
    public static int getCostOfMail(ChainedRoute route, MailType mail) {
        return route.overallCost(mail.getWeight().intValue(), mail.getVolume().intValue());
    }

    /**
     * Get map of mail types and their actual delivery time.
     *
     * @param events
     * @param usedRoutes
     * @return
     */
    public static Map<MailType, Integer> getDeliveryTimes(Map<MailType, ChainedRoute> usedRoutes) {
        Map<MailType, Integer> result = new HashMap<MailType, Integer>();
        for (MailType m : usedRoutes.keySet()) {
            result.put(m, getDeliveryTime(usedRoutes.get(m), m));
        }
        return result;
    }

    /**
     * Get delivery time for route and mail item
     *
     * @param route
     * @param mail
     * @return
     */
    public static int getDeliveryTime(ChainedRoute route, MailType mail) {
        return route.duration(mail.getDate());
    }

    /**
     * Get the price to pay for a route
     *
     * @param events
     * @param route
     * @param priority
     * @param weight
     * @param volume
     * @return int price
     * @throws JAXBException
     */
    public static int getPriceForRoute(List<Event> events, ChainedRoute route, String priority, int weight, int volume) {
        int result = 0;
        Set<PriceType> prices = Events.getPriceTypes(events);
        String from;
        String to;
        PriceType price = null;
        if (Routes.inNewzealand(route.getStart())) {
            from = "New Zealand";
        } else {
            from = route.getStart();
        }
        if (Routes.inNewzealand(route.getEnd())) {
            to = "New Zealand";
        } else {
            to = route.getEnd();
        }
        for (PriceType p : prices) {
            if (p.getFrom().equals(from) && p.getTo().equals(to) && p.getPriority().equals(priority)) {
                price = p;
            }
        }
        if (price == null) {
            return -1;
        } else {
            result += price.getWeightcost().intValue() * weight + price.getVolumecost().intValue() * volume;
        }
        return result;
    }

    /**
     * Get list of routes for origin
     *
     * @param events
     * @param origin
     * @return list of routes for origin
     * @throws JAXBException
     */
    public static List<ChainedRoute> getRoutes(List<Event> events, String origin) {
        List<ChainedRoute> result = new LinkedList<ChainedRoute>();
        List<CostType> starts = new LinkedList<CostType>();
        List<CostType> unused = new LinkedList<CostType>(Events.getActiveCostTypes(events));
        for (CostType c : Events.getActiveCostTypes(events)) {
            if (c.getFrom().equals(origin)) {
                starts.add(c);
                unused.remove(c);
            }
        }
        for (CostType c : starts) {
            List<CostType> costs = new ArrayList<CostType>(1);
            costs.add(c);
            result.add(Routes.chainedRoute(costs));
            result.addAll(getRoutes(c, costs, new ArrayList<CostType>(unused)));
        }
        return result;
    }

    /**
     * Recursive helper function for getRoutes
     *
     * @param lastC
     * @param used
     * @param unused
     * @return List<ChainedRoute>
     */
    private static List<ChainedRoute> getRoutes(CostType lastC, List<CostType> used, List<CostType> unused) {
        List<ChainedRoute> result = new LinkedList<ChainedRoute>();
        List<CostType> newUnused = new ArrayList<CostType>(unused);
        for (CostType c : unused) {
            if (lastC.getTo().equals(c.getFrom()) && !c.getTo().equals(used.get(0).getFrom())) {
                List<CostType> newUsed = new ArrayList<CostType>(used);
                newUsed.add(c);
                newUnused.remove(c);
                result.add(Routes.chainedRoute(newUsed));
                result.addAll(getRoutes(c, newUsed, new ArrayList<CostType>(newUnused)));
            }
        }
        return result;

    }

    /**
     * Get routes for certain origin and destination
     *
     * @param events
     * @param origin
     * @param destination
     * @return List<ChainedRoute>
     * @throws JAXBException
     */
    public static List<ChainedRoute> getRoutes(List<Event> events, String origin, String destination) {
        List<ChainedRoute> result = new LinkedList<ChainedRoute>();
        for (ChainedRoute c : getRoutes(events, origin)) {
            if (c.getEnd().equals(destination)) {
                result.add(c);
            }
        }
        return result;
    }

    /**
     * Helper function to get routes for certain origin, destination and
     * priority
     *
     * @param events
     * @param origin
     * @param destination
     * @param priority
     * @return
     * @throws JAXBException
     */
    private static List<ChainedRoute> getRoutes(List<Event> events, String origin, String destination, String priority) {
        List<ChainedRoute> result = new LinkedList<ChainedRoute>();
        for (ChainedRoute c : getRoutes(events, origin, destination)) {
            if (c.overallPossiblePriorities().contains(priority)) {
                result.add(c);
            }
        }
        return result;
    }

    /**
     * Helper function to compute possible routes for certain, origin,
     * destination, priority, weight and volume
     *
     * @param events
     * @param origin
     * @param destination
     * @param priority
     * @param weight
     * @param volume
     * @return
     * @throws JAXBException
     */
    private static List<ChainedRoute> getRoutes(List<Event> events, String origin, String destination, String priority, int weight, int volume) {
        List<ChainedRoute> result = new LinkedList<ChainedRoute>();
        for (ChainedRoute c : getRoutes(events, origin, destination, priority)) {
            if ((c.overallVolumelimitation() >= volume) && (c.overallWeightlimitation() >= weight)) {
                result.add(c);
            }
        }
        return result;
    }

    /**
     * Get route with the lowest costs for certain origin, destination,
     * priority, weight and volume
     *
     * @param events
     * @param origin
     * @param destination
     * @param priority
     * @param weight
     * @param volume
     * @return route or null, if no route exists
     * @throws JAXBException
     */
    public static ChainedRoute getRoute(List<Event> events, String origin, String destination, String priority, int weight, int volume) {
        int minCost = Integer.MAX_VALUE;
        ChainedRoute minRoute = null;
        for (ChainedRoute c : getRoutes(events, origin, destination, priority, weight, volume)) {
            if (c.overallCost(weight, volume) < minCost) {
                minRoute = c;
            }
        }
        return minRoute;
    }

    /**
     * Get route with the lowest costs for certain mail type
     *
     * @param events
     * @param mail
     * @return route or null, if no route exists
     * @throws JAXBException
     */
    public static ChainedRoute getRoute(List<Event> events, MailType mail) {
        return getRoute(events, mail.getFrom(), mail.getTo(), mail.getPriority(), mail.getWeight().intValue(), mail.getVolume().intValue());
    }

    /**
     * Get the number for a certain day (uses ISO standard for weekday
     * numeration)
     *
     * @param day
     * @return int for day, if day invalid: -1
     */
    public static int noForDay(String day) {
        Map<String, Integer> map = new HashMap<String, Integer>();
        map.put("Monday", 1);
        map.put("Tuesday", 2);
        map.put("Wednesday", 3);
        map.put("Thursday", 4);
        map.put("Friday", 5);
        map.put("Saturday", 6);
        map.put("Sunday", 7);
        if (map.get(day) == null) {
            return -1;
        } else {
            return map.get(day);
        }
    }

    /**
     * Get the day for a given number (uses ISO standard for weekday numeration)
     *
     * @param day
     * @return number of weekday
     */
    public static String dayForNo(int day) {
        Map<Integer, String> map = new HashMap<Integer, String>();
        map.put(1, "Monday");
        map.put(2, "Tuesday");
        map.put(3, "Wednesday");
        map.put(4, "Thursday");
        map.put(5, "Friday");
        map.put(6, "Saturday");
        map.put(7, "Sunday");
        if (map.get(day) == null) {
            return "No Day";
        } else {
            return map.get(day);
        }
    }

    /**
     * Checks if a city is in New Zealand
     *
     * @param place
     * @return boolean
     */
    public static boolean inNewzealand(String place) {
        return DISTRIBUTION_CENTRES.contains(place);
    }

    /**
     * Get a collection of days as String
     *
     * @return
     */
    public static Collection<String> getDays() {
        return new ArrayList<String>(DAYS);
    }

    /**
     * Get a collection of all priorities as String
     *
     * @return
     */
    public static Collection<String> getPriorities() {
        return new ArrayList<String>(PRIORITIES);
    }

    /**
     * Get a collection of all route types as String
     *
     * @return
     */
    public static Collection<String> getRouteTypes() {
        return new ArrayList<String>(ROUTE_TYPES);
    }

    /**
     * THE method to create a chained routes
     *
     * @param costs
     * @return chainedRoute
     */
    public static ChainedRoute chainedRoute(List<CostType> costs) {
        return ChainedRoute.create(costs);
    }

    /**
     * Get the destinations as collection of String for list of routes
     *
     * @param routes
     * @return
     */
    public static Collection<String> getDestinationsForRoutes(List<ChainedRoute> routes) {
        Set<String> result = new HashSet<String>();
        for (ChainedRoute c : routes) {
            result.add(c.getEnd());
        }
        return result;
    }

    /**
     * Get the priorities used in a list of routes as collection if String
     *
     * @param routes
     * @return priorities
     */
    public static Collection<String> getPrioritiesForRoutes(List<ChainedRoute> routes) {
        Set<String> result = new HashSet<String>();
        for (ChainedRoute c : routes) {
            result.addAll(c.overallPossiblePriorities());
        }
        return result;
    }
}
